/******************************************************************************
 * * hal_http.c - http interface
 *
 * *(C) Copyright 2019 Asr International Ltd.
 * * All Rights Reserved
 * ******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include  <stdarg.h>
#include "dm/plat_types.h"
#include "dm/libhttpclient.h"
#include "dm/httpclient_sys.h"
#include "hal.h"
#include "hal_http.h"

#ifdef BUILD_IN_PC_SIMULATOR
#include "curl.h"
#endif

#define HTTP_DEBUG      1

#if HTTP_DEBUG
#define http_printf printf
#else
#define http_printf
#endif

#define HTTP_RETRY_DELAY 400  //2s
#define HTTP_MAX_THREAD 10
typedef struct
{
    uint32_t http_handle_cnt;
} hal_http_mng_t;

enum
{
    HTTP_MSG_SEND_REQ = 0,
    HTTP_MSG_UNKNOW
};
typedef uint8_t hal_http_msg_id_t;

typedef struct
{
    hal_http_msg_id_t msg_id;
    void * msg_data;
} hal_http_msg_t;

static hal_http_mng_t http_mng = {};

#ifndef BUILD_IN_PC_SIMULATOR
static void hal_http_thread_entry(void * para);
#endif

void hal_http_mng_init(void)
{

}

static int http_response_cb(char *data, int len, int num, void *cbdata)
{
    http_printf("%s\n", __FUNCTION__);

    char * temp;
    hal_http_send_cnf_t * send_cnf = (hal_http_send_cnf_t *)cbdata;
    http_printf("%s:send_cnf->resp is %p,send_cnf->resp_len is %d ,len is %d, num is %d\n",__FUNCTION__,send_cnf->resp,send_cnf->resp_len,len,num);
#ifndef BUILD_IN_PC_SIMULATOR
    if (send_cnf->resp) {
        temp = lv_mem_realloc(send_cnf->resp, send_cnf->resp_len + len );
        send_cnf->resp = temp;
        memset(&send_cnf->resp[send_cnf->resp_len], 0, len);
    } else {
        send_cnf->resp = lv_mem_alloc(len);
        memset(send_cnf->resp,0,len);
    }

    LV_ASSERT_MEM(send_cnf->resp);
    if (!send_cnf->resp) {
        http_printf("%s Malloc failed...\n", __FUNCTION__);
        return -1;
    }

    memcpy(&send_cnf->resp[send_cnf->resp_len], data, len);
    send_cnf->resp_len += len;

    return len;
#else
    if (send_cnf->resp) {
        temp = lv_mem_realloc(send_cnf->resp, send_cnf->resp_len + len * num );
        send_cnf->resp = temp;
        memset(&send_cnf->resp[send_cnf->resp_len], 0, (len * num));
    } else {
        send_cnf->resp = lv_mem_alloc(len * num);
        memset(send_cnf->resp,0,(len * num));
    }

    LV_ASSERT_MEM(send_cnf->resp);
    if (!send_cnf->resp) {
        http_printf("%s Malloc failed...\n", __FUNCTION__);
        return -1;
    }

    memcpy(&send_cnf->resp[send_cnf->resp_len], data, len * num);
    send_cnf->resp_len += len * num;

    return len * num;
#endif
}

static void hal_http_send_req_handle(void * msg_data)
{
    hal_http_send_req_t * send_req = (hal_http_send_req_t *)msg_data;
    struct http_client * client = NULL;
#ifdef BUILD_IN_PC_SIMULATOR
    struct http_client_list *header = NULL;
#endif
    uint8_t http_method;
    int response_code = 0;
    uint8_t retry = 0;

    if(!send_req->send_cnf) return;
    hal_http_send_cnf_t * send_cnf = lv_mem_alloc(sizeof(hal_http_send_cnf_t));
    LV_ASSERT_MEM(send_cnf);
    memset(send_cnf, 0, sizeof(hal_http_send_cnf_t));
    send_cnf->http_handle = send_req->http_handle;
    send_cnf->free_ptr = send_req->free_ptr;
    send_cnf->free_num = send_req->free_num;

    //set
#ifdef BUILD_IN_PC_SIMULATOR
    for (uint8_t i = 0;i < 5;i++)
    {
        if (send_req->http_header[i])
        {
            header = http_client_list_append(header, send_req->http_header[i]);
        }
        http_printf("%s:send_req->http_header[%d] is %s\n",__FUNCTION__,i,send_req->http_header[i]);
    }
#endif

    http_printf("%s register url: %s\n", __FUNCTION__, send_req->url);
    while(retry < 2) {
        retry++;
        client = send_req->http_handle->client;
        http_client_setopt(client, HTTPCLIENT_OPT_URL, send_req->url);
        http_client_setopt(client, HTTPCLIENT_OPT_HTTP1_0, 0);	/*"0" is HTTP 1.1, "1" is HTTP 1.0 */
        http_client_setopt(client, HTTPCLIENT_OPT_RESPONSECB, http_response_cb);	/*response callback */
        http_client_setopt(client, HTTPCLIENT_OPT_RESPONSECB_DATA, send_cnf);	/*response callback arg*/

        switch(send_req->act_type) {
        case HTTP_CLIENT_GET:
            http_method = HTTPCLIENT_REQUEST_GET;
            break;
        case HTTP_CLIENT_POST:
            http_method = HTTPCLIENT_REQUEST_POST;
            break;
        default:
            break;
        }

        http_client_setopt(client, HTTPCLIENT_OPT_METHOD, http_method);	/*set method,support GET/POST/PUT */
        http_client_setopt(client, HTTPCLIENT_OPT_POSTDATA, send_req->data);	/*post data is http context */
        http_client_setopt(client, HTTPCLIENT_OPT_POSTLENGTH, send_req->data_len);	/*http context length */

        /*set http private header,our http stack already support Referer/Host/Authorization/User-Agent/Connection/cookie/Content-Length,
          this header is other http header,ex: Content-Type..., we call it private header, */
#ifdef BUILD_IN_PC_SIMULATOR
        http_client_setopt(client, HTTPCLIENT_OPT_HTTPHEADER, header);
#endif
        http_client_getinfo(client, HTTPCLIENT_GETINFO_TCP_STATE, &response_code);
        http_printf("%s Get tcp state %d\n", __FUNCTION__, response_code);
        if (response_code != HTTP_TCP_ESTABLISHED) {
            http_printf("%s http tcp not connecting\n", __FUNCTION__);
            http_client_shutdown(client);	/*release http resources */
            uos_sleep(HTTP_RETRY_DELAY);
            send_req->http_handle->client = http_client_init();
            continue;
        }
#ifndef BUILD_IN_PC_SIMULATOR
        http_client_setopt(client, HTTPCLIENT_OPT_NO_REFERER, 1);
#endif
        response_code = http_client_perform(client);	/*execute http send and recv */
        if (response_code != HTTP_CLIENT_OK) {
            http_printf("%s http perform have some wrong[%d]\n", __FUNCTION__, response_code);
            http_client_shutdown(client);	/*release http resources */
            uos_sleep(HTTP_RETRY_DELAY);
            send_req->http_handle->client = http_client_init();
            if(send_cnf->resp)
            {
                lv_mem_free(send_cnf->resp);
                send_cnf->resp = NULL;
            }
            send_cnf->resp_len = 0;
            continue;
        }
        send_cnf->result = HTTP_RESULT_TIME_OUT;
        http_client_getinfo(client, HTTPCLIENT_GETINFO_RESPONSE_CODE, &response_code);
        http_printf("%s http perform response is 'HTTP OK'(%d)\n", __FUNCTION__, response_code);
        if (response_code == 200) {
            http_printf("%s http perform SUCCESS %d\n", __FUNCTION__, response_code);
            send_cnf->result = HTTP_RESULT_SUCCESS;
            break;
        } else {
            http_printf("%s http perform FAIL %d\n", __FUNCTION__, response_code);
            if(send_cnf->resp)
            {
                lv_mem_free(send_cnf->resp);
                send_cnf->resp = NULL;
            }
            send_cnf->resp_len = 0;
        }
    }

    send_req->send_cnf(send_cnf);
#ifndef BUILD_IN_PC_SIMULATOR
    extern void watch_qq_net_mng_del_msg(hal_http_handle_t * http_handle);
    watch_qq_net_mng_del_msg(send_req->http_handle);
#endif
}

hal_http_handle_t * hal_http_client_init(void)
{
    hal_http_handle_t * http_handle;

    http_printf("%s:http_mng.http_handle_cnt is %d\n",__FUNCTION__,http_mng.http_handle_cnt);
    //support max HTTP_MAX_THREAD concurrent http accessing
    if(HTTP_MAX_THREAD == http_mng.http_handle_cnt) return NULL;

    http_handle = lv_mem_alloc(sizeof(hal_http_handle_t));
    if(NULL == http_handle) return NULL;
    memset(http_handle, 0, sizeof(hal_http_handle_t));

    http_handle->thread_name = lv_mem_alloc(30);
    snprintf(http_handle->thread_name, 30, "http_task_%p", http_handle);
#ifndef BUILD_IN_PC_SIMULATOR
    TASK_HANDLE * http_thread = uos_create_task(hal_http_thread_entry, &(http_handle->http_thread), 10, 0x2000, 199, http_handle->thread_name);
    if(NULL == http_thread)
    {
        http_printf("%s:http_thread is NULL\n",__FUNCTION__);
        return NULL;
    }
    http_handle->http_thread = http_thread;
    http_printf("%s:http_thread is %p,task_id is %d\n",__FUNCTION__,http_thread,(TASK_ID)http_handle->http_thread);
#endif
    http_mng.http_handle_cnt++;
    http_handle->http_status = HTTP_CLINET_INITED;
    http_handle->client = http_client_init();

    return http_handle;
}

void hal_http_client_del(hal_http_handle_t * http_handle)
{
    http_printf("%s:http_thread is %p,task_id is %d\n", __FUNCTION__, http_handle->http_thread,(TASK_ID)http_handle->http_thread);
    http_mng.http_handle_cnt--;
#ifndef BUILD_IN_PC_SIMULATOR
    uos_delete_task(http_handle->http_thread);
    lv_mem_free(http_handle->thread_name);
#endif
    http_client_shutdown(http_handle->client);
    lv_mem_free(http_handle);
}

#ifndef BUILD_IN_PC_SIMULATOR
static void hal_http_msg_hadle(hal_http_msg_t * msg)
{
    hal_http_msg_id_t msg_id = msg->msg_id;
    hal_http_send_req_t * send_req = (hal_http_send_req_t *)msg->msg_data;

    switch(msg_id) {
    case HTTP_MSG_SEND_REQ:
        hal_http_send_req_handle(msg->msg_data);
        if(send_req->url) lv_mem_free(send_req->url);
        if(send_req->data_len) lv_mem_free(send_req->data);
        for (uint8_t i = 0; i < 5; i++)
        {
            if(send_req->http_header[i])
            {
                lv_mem_free(send_req->http_header[i]);
            }
        }
        lv_mem_free(send_req);
        break;
    default:
        break;
    }
}

static void hal_http_thread_entry(void * para)
{
    hal_http_msg_t * msg;

    while(1) {
        TASK_HANDLE * http_thread = *((TASK_HANDLE **)para);
        TASK_ID task_id = (TASK_ID)(*((TASK_HANDLE **)para));
        http_printf("%s:http_thread is %p,task_id is %d\n",__FUNCTION__,http_thread,task_id);
        if(task_id == 0)
        {
            uos_sleep(10);
            continue;
        }
        msg = (hal_http_msg_t *)Hal_Receive_Message(task_id, true);
        if(NULL == msg) continue;
        hal_http_msg_hadle(msg);
        lv_mem_free(msg);
    }
}

void hal_http_send_req(hal_http_send_req_t * http_send_req)
{
    hal_http_handle_t * http_handle = http_send_req->http_handle;
    hal_http_msg_t * msg = (hal_http_msg_t *)lv_mem_alloc(sizeof(hal_http_msg_t));
    msg->msg_id = HTTP_MSG_SEND_REQ;

    msg->msg_data = (void *)http_send_req;
    Hal_Send_Message((TASK_ID)http_handle->http_thread, msg);
    http_printf("%s:http_thread is %p,task_id is %d\n",__FUNCTION__,http_handle->http_thread,(TASK_ID)http_handle->http_thread);
}

#else
void hal_http_send_req(hal_http_send_req_t * http_send_req)
{
    hal_http_send_req_handle(http_send_req);
}

struct http_client * http_client_init(void)
{
    CURLcode return_code;
    CURL * http_handle;

    return_code= curl_global_init(CURL_GLOBAL_ALL);

    if (CURLE_OK != return_code) {
        printf("initlibcurl failed.\n");
        return NULL;
    }

    //获取easy handle

    http_handle = curl_easy_init();

    if (!http_handle) {
        return NULL;
    }

    curl_easy_setopt(http_handle, CURLOPT_HEADER, 0);
    curl_easy_setopt(http_handle, CURLOPT_SSL_VERIFYHOST, 0);
    curl_easy_setopt(http_handle, CURLOPT_SSL_VERIFYPEER, 0);
    return (http_client *)http_handle;
}

int http_client_setopt(struct http_client *client, int tag, ...)
{
    va_list arg;
    int opt;
    va_start(arg, tag);

    switch(tag) {
    case HTTPCLIENT_OPT_URL:
        curl_easy_setopt((CURL *)client, CURLOPT_URL, va_arg(arg, char *));
        break;
    case HTTPCLIENT_OPT_HTTP1_0:
        opt = va_arg(arg, int);
        if(opt == 0) {
            curl_easy_setopt((CURL *)client, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        } else {
            curl_easy_setopt((CURL *)client, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
        }

        break;
    case HTTPCLIENT_OPT_RESPONSECB:
        curl_easy_setopt((CURL *)client, CURLOPT_WRITEFUNCTION, va_arg(arg, void *));
        break;
    case HTTPCLIENT_OPT_RESPONSECB_DATA:
        curl_easy_setopt((CURL *)client, CURLOPT_WRITEDATA, va_arg(arg, void *));
        break;
    case HTTPCLIENT_OPT_METHOD:
        opt = va_arg(arg, int);
        if(HTTPCLIENT_REQUEST_GET == opt) {
            curl_easy_setopt((CURL *)client, CURLOPT_CUSTOMREQUEST, "GET");
        } else if (HTTPCLIENT_REQUEST_POST == opt){
            curl_easy_setopt((CURL *)client, CURLOPT_CUSTOMREQUEST, "POST");
        } else {
            curl_easy_setopt((CURL *)client, CURLOPT_CUSTOMREQUEST, "GET");
        }
        break;
    case HTTPCLIENT_OPT_POSTDATA:
        curl_easy_setopt((CURL *)client, CURLOPT_POSTFIELDS, va_arg(arg, char *));
        break;
    case HTTPCLIENT_OPT_POSTLENGTH:
        curl_easy_setopt((CURL *)client, CURLOPT_POSTFIELDSIZE, va_arg(arg, int));
        break;
    case HTTPCLIENT_OPT_HTTPHEADER:
        curl_easy_setopt((CURL *)client, CURLOPT_HTTPHEADER, va_arg(arg, void *));
        curl_slist_free_all((struct curl_slist *)va_arg(arg, void *));
        break;
    default:
        printf("%s not support tag %d\n", __FUNCTION__, tag);
        break;
    }

    va_end(arg);

    return 0;
}

struct http_client_list * http_client_list_append(struct http_client_list * list, const char * data)
{
    struct curl_slist * slist = (struct curl_slist *)list;
    slist =  curl_slist_append(slist, data);
    return ((struct http_client_list *)slist);
}

int http_client_getinfo(struct http_client * client, int tag, void * result)
{
    if(HTTPCLIENT_GETINFO_TCP_STATE == tag) *(uint8_t *)result = HTTP_TCP_ESTABLISHED;
    if(HTTPCLIENT_GETINFO_RESPONSE_CODE == tag) {
        curl_easy_getinfo((CURL *)client, CURLINFO_RESPONSE_CODE, result);
    }

    return 1;
}

void http_client_shutdown(struct http_client * client)
{
    curl_easy_cleanup((CURL *)client);
    curl_global_cleanup();
}

int http_client_perform(struct http_client *client)
{
    CURLcode res = curl_easy_perform((CURL *)client);
    switch(res) {
    case CURLE_OK:
        return HTTP_CLIENT_OK;
    default:
        break;
    }
    return res;
}

#endif

//回调函数，将接收到的数据保存到本地文件中，同时显示在控制台上。

static size_t write_data(void *buf, size_t size, size_t nmemb, void *userp)
{
//    memcpy(userp, buf, 800);
    printf("%s buf \n%s\n", __FUNCTION__, (char*)buf);
    return size;
}

#if 0
int http_test(void)
{
    char url[] = "https://www.baidu.com";
    printf("%s open %s\n", __FUNCTION__, url);

    struct http_client * http_handle;

    http_handle = http_client_init();

    //通过CURLOPT_URL属性设置url
    curl_easy_setopt(http_handle, CURLOPT_URL, url);
    curl_easy_setopt(http_handle, CURLOPT_CUSTOMREQUEST, "GET");
    curl_easy_setopt(http_handle, CURLOPT_SSL_VERIFYHOST, 0);
    curl_easy_setopt(http_handle, CURLOPT_SSL_VERIFYPEER, 0);

    //注册回调函数write_cb，回调函数将会在接收到数据的时候被调用
    curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION, write_data);

    //通过CURLOPT_USERPWD属性来设置用户名与密码。参数是”user:password “的字符串
    char * userp  = malloc(10000);
    if (userp) {
        curl_easy_setopt(http_handle, CURLOPT_WRITEDATA, userp);
    }

    CURLcode res;
    res = curl_easy_perform(http_handle);
    printf("%s res %d\n%s\n", __FUNCTION__, res, curl_easy_strerror(res));

    int response_code;
    http_client_getinfo(http_handle, HTTPCLIENT_GETINFO_RESPONSE_CODE, &response_code);
    printf("%s response code %d\n", __FUNCTION__, response_code);

    http_client_shutdown(http_handle);

    return 0;
}
#else
int http_test(void)
{
    printf("%s\n", __FUNCTION__);
    char url[] = "https://www.baidu.com";
    struct http_client * http_handle;
    char * userp  = malloc(10000);
    http_handle = http_client_init();

    http_client_setopt(http_handle, HTTPCLIENT_OPT_URL, url);
    http_client_setopt(http_handle, HTTPCLIENT_OPT_HTTP1_0, 0);	/*"0" is HTTP 1.1, "1" is HTTP 1.0 */
    http_client_setopt(http_handle, HTTPCLIENT_OPT_RESPONSECB, write_data);	/*response callback */
    http_client_setopt(http_handle, HTTPCLIENT_OPT_RESPONSECB_DATA, userp);	/*response callback arg*/
    http_client_setopt(http_handle, HTTPCLIENT_OPT_METHOD, HTTP_CLIENT_GET);

    http_client_perform(http_handle);

    int response_code;
    http_client_getinfo(http_handle, HTTPCLIENT_GETINFO_RESPONSE_CODE, &response_code);
    printf("%s response code %d\n", __FUNCTION__, response_code);

    http_client_shutdown(http_handle);
    return 0;
}
#endif
